//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.eResearch.WFServiceLibrary.SchedulerLogic
{
    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    using V1 = TaskScheduler.V1;
    using V2 = TaskScheduler;

    /// <summary>
    /// Abstract base class that provides the common properties that are inherited by 
    /// all action objects. 
    /// An action object is created by the <see cref="ActionCollection.Add"/> method.
    /// </summary>
    public abstract class Action : IDisposable
    {
        internal V2.IAction iAction;

        /// <summary>
        /// Gets or sets the unbound values.
        /// </summary>
        /// <value>The unbound values.</value>
        public Dictionary<string, object> UnboundValues { get; set; }

        /// <summary>
        /// Gets a value indicating whether this <see cref="Action"/> is bound.
        /// </summary>
        /// <value><c>true</c> if bound; otherwise, <c>false</c>.</value>
        internal virtual bool Bound { get { return this.iAction != null; } }

        /// <summary>
        /// Initializes a new instance of the <see cref="Action"/> class.
        /// </summary>
        internal Action()
        {
            this.UnboundValues = new Dictionary<string, object>();
        }

        /// <summary>
        /// Binds the specified task definition.
        /// </summary>
        internal virtual void Bind(V1.ITask iTask)
        {
        }

        /// <summary>
        /// Binds the specified task definition.
        /// </summary>
        /// <param name="iTaskDef">Task Definition.</param>
        internal virtual void Bind(V2.ITaskDefinition iTaskDef)
        {
            V2.IActionCollection iActions = iTaskDef.Actions;
            switch (this.GetType().Name)
            {
                case "ExecAction":
                    iAction = iActions.Create(V2._TASK_ACTION_TYPE.TASK_ACTION_EXEC);
                    break;
                default:
                    throw new ArgumentException();
            }

            Marshal.ReleaseComObject(iActions);

            foreach (string key in UnboundValues.Keys)
            {
                try
                {
                    iAction.GetType().InvokeMember(key, System.Reflection.BindingFlags.SetProperty, null, iAction, new object[] { UnboundValues[key] });
                }
                catch (System.Reflection.TargetInvocationException tie) { throw tie.InnerException; }
            }

            UnboundValues.Clear();
            UnboundValues = null;
        }

        /// <summary>
        /// Creates a specialized class from a defined interface.
        /// </summary>
        /// <param name="iAction">The i action.</param>
        /// <returns></returns>
        internal static Action CreateAction(V2.IAction iAction)
        {
            switch (iAction.Type)
            {
                case V2._TASK_ACTION_TYPE.TASK_ACTION_EXEC:
                default:
                    return new ExecAction((V2.IExecAction)iAction);
            }
        }

        /// <summary>
        /// Releases all resources used by this class.
        /// </summary>
        public virtual void Dispose()
        {
            if (iAction != null)
                Marshal.ReleaseComObject(iAction);
        }
    }

    /// <summary>
    /// Represents an action that executes a command-line operation.
    /// </summary>
    public sealed class ExecAction : Action
    {
        private V1.ITask v1Task;

        /// <summary>
        /// Gets or sets the path to an executable file.
        /// </summary>
        public string Path
        {
            get
            {
                if (v1Task != null)
                    return v1Task.GetApplicationName();
                if (iAction != null)
                    return ((V2.IExecAction)iAction).Path;
                return UnboundValues.ContainsKey("Path") ? (string)UnboundValues["Path"] : null;
            }
            set
            {
                if (v1Task != null)
                    v1Task.SetApplicationName(value);
                else if (iAction != null)
                    ((V2.IExecAction)iAction).Path = value;
                else
                    UnboundValues["Path"] = value;
            }
        }

        /// <summary>
        /// Gets or sets the arguments associated with the command-line operation.
        /// </summary>
        public string Arguments
        {
            get
            {
                if (v1Task != null)
                    return v1Task.GetParameters();
                if (iAction != null)
                    return ((V2.IExecAction)iAction).Arguments;
                return UnboundValues.ContainsKey("Arguments") ? (string)UnboundValues["Arguments"] : null;
            }
            set
            {
                if (v1Task != null)
                    v1Task.SetParameters(value);
                else if (iAction != null)
                    ((V2.IExecAction)iAction).Arguments = value;
                else
                    UnboundValues["Arguments"] = value;
            }
        }

        /// <summary>
        /// Gets or sets the directory that contains either the executable file or the 
        /// files that are used by the executable file.
        /// </summary>
        public string WorkingDirectory
        {
            get
            {
                if (v1Task != null)
                    return v1Task.GetWorkingDirectory();
                if (iAction != null)
                    return ((V2.IExecAction)iAction).WorkingDirectory;
                return UnboundValues.ContainsKey("WorkingDirectory") ? (string)UnboundValues["WorkingDirectory"] : null;
            }
            set
            {
                if (v1Task != null)
                    v1Task.SetWorkingDirectory(value);
                else if (iAction != null)
                    ((V2.IExecAction)iAction).WorkingDirectory = value;
                else
                    UnboundValues["WorkingDirectory"] = value;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ExecAction"/> class.
        /// </summary>
        /// <param name="action">The action.</param>
        internal ExecAction(V2.IExecAction action)
        {
            iAction = action;
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="ExecAction"/> class.
        /// </summary>
        /// <param name="task">The task.</param>
        internal ExecAction(V1.ITask task)
        {
            v1Task = task;
        }

        /// <summary>
        /// Creates a new instance of an <see cref="ExecAction"/> that can be added to <see cref="TaskDefinition.Actions"/>.
        /// </summary>
        /// <param name="path">Path to an executable file.</param>
        /// <param name="arguments">Arguments associated with the command-line operation. This value can be null.</param>
        /// <param name="workingDirectory">Directory that contains either the executable file or the files that are used by the executable file. This value can be null.</param>
        public ExecAction(string path, string arguments, string workingDirectory)
        {
            this.Path = path;
            this.Arguments = arguments;
            this.WorkingDirectory = workingDirectory;
        }

        /// <summary>
        /// Gets a value indicating whether this <see cref="ExecAction"/> is bound.
        /// </summary>
        /// <value><c>true</c> if bound; otherwise, <c>false</c>.</value>
        internal override bool Bound
        {
            get
            {
                if (v1Task != null)
                    return true;
                return base.Bound;
            }
        }

        /// <summary>
        /// Binds the specified v1 task.
        /// </summary>
        /// <param name="v1Task">The v1 task.</param>
        internal override void Bind(V1.ITask v1Task)
        {
            object o;
            if (UnboundValues.TryGetValue("Path", out o) && o != null)
                v1Task.SetApplicationName((string)o);
            if (UnboundValues.TryGetValue("Arguments", out o) && o != null)
                v1Task.SetParameters((string)o);
            if (UnboundValues.TryGetValue("WorkingDirectory", out o) && o != null)
                v1Task.SetWorkingDirectory((string)o);
        }
    }
}
